using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace SolarWinds.Helpers
{
    public class DrawableComponentManager : DrawableGameComponent
    {
        private readonly GameComponentCollection mComponents;
        private readonly List<IUpdateable> mUpdateables;
        private readonly List<IDrawable> mDrawables;
        private readonly List<IGameComponent> mNotInitialized;

        public GameComponentCollection Components
        {
            get { return mComponents; }
        }

        public DrawableComponentManager(Game game)
            : base(game)
        {
            mComponents = new GameComponentCollection();
            mComponents.ComponentAdded += OnComponentAdded;
            mComponents.ComponentRemoved += OnComponentRemoved;

            mUpdateables = new List<IUpdateable>();
            mDrawables = new List<IDrawable>();
            mNotInitialized = new List<IGameComponent>();

            EnabledChanged += ComponentManagerEnabledChanged;
            VisibleChanged += ComponentManagerVisibleChanged;
        }

        void ComponentManagerVisibleChanged(object sender, EventArgs e)
        {
            foreach (DrawableGameComponent drawable in mDrawables)
            {
                drawable.Visible = Visible;
            }
        }

        void ComponentManagerEnabledChanged(object sender, EventArgs e)
        {
            foreach (GameComponent component in mComponents)
            {
                component.Enabled = Enabled;
            }
        }

        public override void Initialize()
        {
            base.Initialize();

            while (mNotInitialized.Count != 0)
            {
                mNotInitialized[0].Initialize();
                mNotInitialized.RemoveAt(0);
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                lock (this)
                {
                    var array = new IGameComponent[mComponents.Count];
                    mComponents.CopyTo(array, 0);
                    for (int i = 0; i < array.Length; i++)
                    {
                        var disposable = array[i] as IDisposable;
                        if (disposable != null)
                        {
                            disposable.Dispose();
                        }
                    }
                }
            }

            base.Dispose(disposing);
        }

        void OnComponentRemoved(object sender, GameComponentCollectionEventArgs e)
        {
            if (!SolarWindsGame.IsRunning)
            {
                mNotInitialized.Remove(e.GameComponent);
            }

            var disposable = e.GameComponent as GameComponent;
            if (disposable != null)
            {
                disposable.Disposed -= OnComponentDisposed;
            }

            var updateable = e.GameComponent as IUpdateable;
            if (updateable != null)
            {
                mUpdateables.Remove(updateable);
                updateable.UpdateOrderChanged -= OnUpdateableUpdateOrderChanged;
            }

            var drawable = e.GameComponent as IDrawable;
            if (drawable != null)
            {
                mDrawables.Remove(drawable);
                drawable.DrawOrderChanged -= OnDrawableDrawOrderChanged;
            }
        }

        void OnComponentAdded(object sender, GameComponentCollectionEventArgs e)
        {
            if (SolarWindsGame.IsRunning)
            {
                e.GameComponent.Initialize();
            }
            else
            {
                mNotInitialized.Add(e.GameComponent);
            }

            var disposable = e.GameComponent as GameComponent;
            if (disposable != null)
            {
                disposable.Disposed += OnComponentDisposed;
            }

            var updateable = e.GameComponent as IUpdateable;
            if (updateable != null)
            {
                int index = mUpdateables.BinarySearch(updateable, UpdateOrderComparer.Default);
                if (index < 0)
                {
                    index = ~index;
                    while ((index < mUpdateables.Count) && 
                           (mUpdateables[index].UpdateOrder == updateable.UpdateOrder))
                    {
                        index++;
                    }

                    mUpdateables.Insert(index, updateable);
                    updateable.UpdateOrderChanged += OnUpdateableUpdateOrderChanged;
                }
            }

            var drawable = e.GameComponent as IDrawable;
            if (drawable != null)
            {
                int index = mDrawables.BinarySearch(drawable, DrawOrderComparer.Default);
                if (index < 0)
                {
                    index = ~index;
                    while ((index < mDrawables.Count) && 
                           (mDrawables[index].DrawOrder == drawable.DrawOrder))
                    {
                        index++;
                    }

                    mDrawables.Insert(index, drawable);
                    drawable.DrawOrderChanged += OnDrawableDrawOrderChanged;
                }
            }
        }

        void OnComponentDisposed(object sender, EventArgs e)
        {
            Components.Remove((IGameComponent)sender);
        }

        private void OnDrawableDrawOrderChanged(object sender, EventArgs e)
        {
            var drawable = sender as IDrawable;
            mDrawables.Remove(drawable);

            int index = mDrawables.BinarySearch(drawable, DrawOrderComparer.Default);
            if (index < 0)
            {
                index = ~index;
                while ((index < mDrawables.Count) &&
                       (mDrawables[index].DrawOrder == drawable.DrawOrder))
                {
                    index++;
                }

                mDrawables.Insert(index, drawable);
            }
        }

        private void OnUpdateableUpdateOrderChanged(object sender, EventArgs e)
        {
            var updateable = sender as IUpdateable;
            mUpdateables.Remove(updateable);
            int index = mUpdateables.BinarySearch(updateable, UpdateOrderComparer.Default);
            if (index < 0)
            {
                index = ~index;
                while ((index < mUpdateables.Count) && 
                       (mUpdateables[index].UpdateOrder == updateable.UpdateOrder))
                {
                    index++;
                }

                mUpdateables.Insert(index, updateable);
            }
        }
                
        public override void Update(GameTime gameTime)
        {
            if (Enabled)
            {
                base.Update(gameTime);

                for (int i = 0; i < mUpdateables.Count; i++)
                {
                    IUpdateable updateable = mUpdateables[i];
                    if (updateable.Enabled)
                    {
                        updateable.Update(gameTime);
                    }
                }
            }
        }

        public override void Draw(GameTime gameTime)
        {
            if (Visible)
            {
                for (int i = 0; i < mDrawables.Count; i++)
                {
                    IDrawable drawable = mDrawables[i];
                    if (drawable.Visible)
                    {
                        drawable.Draw(gameTime);
                    }
                }
            }
        }
    }
}